# K = 6 and d = 4
import numpy as np
import multiprocessing as mp
import datetime


l_m = [15, 16, 16, 15]
l_n = [8, 6, 6, 8]
C_A = 2
C_B = 2

# k = 1, d = 1
A_1_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_1_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 1, d = 2
A_1_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_1_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 1, d = 3
A_1_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_1_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 1, d = 4
A_1_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_1_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# k = 2, d = 1
A_2_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_2_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 2, d = 2
A_2_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_2_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 2, d = 3
A_2_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_2_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 2, d = 4
A_2_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_2_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# k = 3, d = 1
A_3_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_3_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 3, d = 2
A_3_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_3_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 3, d = 3
A_3_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_3_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 3, d = 4
A_3_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_3_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# k = 4, d = 1
A_4_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_4_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 4, d = 2
A_4_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_4_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 4, d = 3
A_4_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_4_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 4, d = 4
A_4_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_4_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# k = 5, d = 1
A_5_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_5_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 5, d = 2
A_5_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_5_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 5, d = 3
A_5_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_5_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 5, d = 4
A_5_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_5_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# k = 6, d = 1
A_6_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[0], C_A))
B_6_1 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[0], C_B))

# k = 6, d = 2
A_6_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[1], C_A))
B_6_2 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[1], C_B))

# k = 6, d = 3
A_6_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[2], C_A))
B_6_3 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[2], C_B))

# k = 6, d = 4
A_6_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_m[3], C_A))
B_6_4 = np.random.normal(0, 1/np.sqrt(2*np.pi), (l_n[3], C_B))

# x
X = np.random.uniform(0.0, 1.0, l_m)

# global RawArray for multiple processes
g_I = None
shape_I = tuple(l_m)
g_O = None
shape_O = (l_n[0]*l_n[1]*l_m[3], l_m[2])
g_AB_1_1 = None
g_AB_1_2 = None
g_AB_1_3 = None
g_AB_1_4 = None
g_AB_2_1 = None
g_AB_2_2 = None
g_AB_2_3 = None
g_AB_2_4 = None
g_AB_3_1 = None
g_AB_3_2 = None
g_AB_3_3 = None
g_AB_3_4 = None
g_AB_4_1 = None
g_AB_4_2 = None
g_AB_4_3 = None
g_AB_4_4 = None
g_AB_5_1 = None
g_AB_5_2 = None
g_AB_5_3 = None
g_AB_5_4 = None
g_AB_6_1 = None
g_AB_6_2 = None
g_AB_6_3 = None
g_AB_6_4 = None
shape_d_1 = (l_m[0]*C_A, l_n[0]*C_B)
shape_d_2 = (l_m[1]*C_A, l_n[1]*C_B)
shape_d_3 = (l_m[2]*C_A, l_n[2]*C_B)
shape_d_4 = (l_m[3]*C_A, l_n[3]*C_B)

g_lst_serial_times = []
g_lst_parallel_times = []


def normal_algo():
	AB_1_1 = np.kron(A_1_1, B_1_1)
	AB_1_2 = np.kron(A_1_2, B_1_2)
	AB_1_3 = np.kron(A_1_3, B_1_3)
	AB_1_4 = np.kron(A_1_4, B_1_4)
	AB_2_1 = np.kron(A_2_1, B_2_1)
	AB_2_2 = np.kron(A_2_2, B_2_2)
	AB_2_3 = np.kron(A_2_3, B_2_3)
	AB_2_4 = np.kron(A_2_4, B_2_4)
	AB_3_1 = np.kron(A_3_1, B_3_1)
	AB_3_2 = np.kron(A_3_2, B_3_2)
	AB_3_3 = np.kron(A_3_3, B_3_3)
	AB_3_4 = np.kron(A_3_4, B_3_4)
	AB_4_1 = np.kron(A_4_1, B_4_1)
	AB_4_2 = np.kron(A_4_2, B_4_2)
	AB_4_3 = np.kron(A_4_3, B_4_3)
	AB_4_4 = np.kron(A_4_4, B_4_4)
	AB_5_1 = np.kron(A_5_1, B_5_1)
	AB_5_2 = np.kron(A_5_2, B_5_2)
	AB_5_3 = np.kron(A_5_3, B_5_3)
	AB_5_4 = np.kron(A_5_4, B_5_4)
	AB_6_1 = np.kron(A_6_1, B_6_1)
	AB_6_2 = np.kron(A_6_2, B_6_2)
	AB_6_3 = np.kron(A_6_3, B_6_3)
	AB_6_4 = np.kron(A_6_4, B_6_4)

	W_1 = np.concatenate([AB_1_1, AB_2_1, AB_3_1, AB_4_1, AB_5_1, AB_6_1], axis = -1)
	W_2 = np.concatenate([AB_1_2, AB_2_2, AB_3_2, AB_4_2, AB_5_2, AB_6_2], axis = -1)
	W_3 = np.concatenate([AB_1_3, AB_2_3, AB_3_3, AB_4_3, AB_5_3, AB_6_3], axis = -1)
	W_4 = np.concatenate([AB_1_4, AB_2_4, AB_3_4, AB_4_4, AB_5_4, AB_6_4], axis = -1)

	start_t = datetime.datetime.now()

	# W_1 to (m_1 * n_1*C)
	W_1 = np.reshape(np.reshape(W_1, (l_m[0], l_n[0], -1)), (l_m[0], -1))

	# Y is (m_2*m_3*m_4 * n_1*C)
	Y = np.matmul(np.reshape(X, (l_m[0], l_m[1]*l_m[2]*l_m[3])).T, W_1)

	# Y to (n_1*m_3*m_4 * m_2*C)
	Y = np.reshape(Y, (l_m[1], l_m[2]*l_m[3], l_n[0], -1))
	Y = np.transpose(Y, (2, 1, 0, 3))
	Y = np.reshape(Y, (l_n[0]*l_m[2]*l_m[3], -1))

	# W_2 to (m_2*C * n_2)
	W_2 = np.reshape(W_2, (l_m[1], l_n[1], -1))
	W_2 = np.transpose(W_2, (0, 2, 1))
	W_2 = np.reshape(W_2, (-1, l_n[1]))

	# Y is (n_1*m_3*m_4 * n_2)
	Y = np.matmul(Y, W_2)

	# Y to (n_1*n_2*m_4 * m_3)
	Y = np.reshape(Y, (l_n[0], l_m[2], l_m[3], l_n[1]))
	Y = np.transpose(Y, (0, 3, 2, 1))
	Y = np.reshape(Y, (l_n[0]*l_n[1]*l_m[3], l_m[2]))

	# W_3 to (m_3 * n_3*C)
	W_3 = np.reshape(np.reshape(W_3, (l_m[2], l_n[2], -1)), (l_m[2], -1))

	# Y is (n_1*n_2*m_4 * n_3*C)
	Y = np.matmul(Y, W_3)

	# Y to (n_1*n_2*n_3 * m_4*C)
	Y = np.reshape(Y, (l_n[0]*l_n[1], l_m[3], l_n[2], -1))
	Y = np.transpose(Y, (0, 2, 1, 3))
	Y = np.reshape(Y, (l_n[0]*l_n[1]*l_n[2], -1))

	# W_4 to (m_4*C * n_4)
	W_4 = np.reshape(W_4, (l_m[3], l_n[3], -1))
	W_4 = np.transpose(W_4, (0, 2, 1))
	W_4 = np.reshape(W_4, (-1, l_n[3]))

	# Y is (n_1*n_2*n_3 * n_4)
	Y = np.reshape(np.matmul(Y, W_4), (-1))

	end_t = datetime.datetime.now()
	print('Whole serial time: %lf' % (end_t - start_t).total_seconds())
	g_lst_serial_times.append((end_t - start_t).total_seconds())

	return Y


def single_algo_1(a, b, c, d):
	I = np.frombuffer(g_I, np.double).reshape(shape_I)
	if a == 1 and b == 1 and c == 1 and d == 2:
		W_1 = np.frombuffer(g_AB_1_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_1_2, np.double).reshape(shape_d_2)
	if a == 2 and b == 1 and c == 2 and d == 2:
		W_1 = np.frombuffer(g_AB_2_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_2_2, np.double).reshape(shape_d_2)
	if a == 3 and b == 1 and c == 3 and d == 2:
		W_1 = np.frombuffer(g_AB_3_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_3_2, np.double).reshape(shape_d_2)
	if a == 4 and b == 1 and c == 4 and d == 2:
		W_1 = np.frombuffer(g_AB_4_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_4_2, np.double).reshape(shape_d_2)
	if a == 5 and b == 1 and c == 5 and d == 2:
		W_1 = np.frombuffer(g_AB_5_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_5_2, np.double).reshape(shape_d_2)
	if a == 6 and b == 1 and c == 6 and d == 2:
		W_1 = np.frombuffer(g_AB_6_1, np.double).reshape(shape_d_1)
		W_2 = np.frombuffer(g_AB_6_2, np.double).reshape(shape_d_2)

	# W_1 to (m_1 * n_1*C)
	W_1 = np.reshape(np.reshape(W_1, (l_m[0], l_n[0], -1)), (l_m[0], -1))

	# O is (m_2*m_3*m_4 * n_1*C)
	O = np.matmul(np.reshape(I, (l_m[0], l_m[1]*l_m[2]*l_m[3])).T, W_1)

	# O to (n_1*m_3*m_4 * m_2*C)
	O = np.reshape(O, (l_m[1], l_m[2]*l_m[3], l_n[0], -1))
	O = np.transpose(O, (2, 1, 0, 3))
	O = np.reshape(O, (l_n[0]*l_m[2]*l_m[3], -1))

	# W_2 to (m_2*C * n_2)
	W_2 = np.reshape(W_2, (l_m[1], l_n[1], -1))
	W_2 = np.transpose(W_2, (0, 2, 1))
	W_2 = np.reshape(W_2, (-1, l_n[1]))

	# O is (n_1*m_3*m_4 * n_2)
	O = np.matmul(O, W_2)

	# O to (n_1*n_2*m_4, m_3)
	O = np.reshape(O, (l_n[0], l_m[2], l_m[3], l_n[1]))
	O = np.transpose(O, (0, 3, 2, 1))
	O = np.reshape(O, (l_n[0]*l_n[1]*l_m[3], l_m[2]))

	return O


def single_algo_2(a, b, c, d):
	O = np.frombuffer(g_O, np.double).reshape(shape_O)
	if a == 1 and b == 3 and c == 1 and d == 4:
		W_3 = np.frombuffer(g_AB_1_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_1_4, np.double).reshape(shape_d_4)
	if a == 2 and b == 3 and c == 2 and d == 4:
		W_3 = np.frombuffer(g_AB_2_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_2_4, np.double).reshape(shape_d_4)
	if a == 3 and b == 3 and c == 3 and d == 4:
		W_3 = np.frombuffer(g_AB_3_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_3_4, np.double).reshape(shape_d_4)
	if a == 4 and b == 3 and c == 4 and d == 4:
		W_3 = np.frombuffer(g_AB_4_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_4_4, np.double).reshape(shape_d_4)
	if a == 5 and b == 3 and c == 5 and d == 4:
		W_3 = np.frombuffer(g_AB_5_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_5_4, np.double).reshape(shape_d_4)
	if a == 6 and b == 3 and c == 6 and d == 4:
		W_3 = np.frombuffer(g_AB_6_3, np.double).reshape(shape_d_3)
		W_4 = np.frombuffer(g_AB_6_4, np.double).reshape(shape_d_4)

	# W_3 to (m_3 * n_3*C)
	W_3 = np.reshape(np.reshape(W_3, (l_m[2], l_n[2], -1)), (l_m[2], -1))

	# O is (n_1*n_2*m_4, n_3*C)
	O = np.matmul(O, W_3)

	# O to (n_1*n_2*n_3, m_4*C)
	O = np.reshape(O, (l_n[0]*l_n[1], l_m[3], l_n[2], -1))
	O = np.transpose(O, (0, 2, 1, 3))
	O = np.reshape(O, (l_n[0]*l_n[1]*l_n[2], -1))

	# W_4 to (m_4*C * n_4)
	W_4 = np.reshape(W_4, (l_m[3], l_n[3], -1))
	W_4 = np.transpose(W_4, (0, 2, 1))
	W_4 = np.reshape(W_4, (-1, l_n[3]))

	# O is (n_1*n_2*n_3, n_4)
	O = np.matmul(O, W_4)

	return np.reshape(O, (-1))


def init_pool_1(arr_AB_1_1,arr_AB_1_2,
				arr_AB_2_1,arr_AB_2_2,
				arr_AB_3_1,arr_AB_3_2,
				arr_AB_4_1,arr_AB_4_2,
				arr_AB_5_1,arr_AB_5_2,
				arr_AB_6_1,arr_AB_6_2,
				arr_I):
	global g_AB_1_1
	g_AB_1_1 = arr_AB_1_1
	global g_AB_1_2
	g_AB_1_2 = arr_AB_1_2
	global g_AB_2_1
	g_AB_2_1 = arr_AB_2_1
	global g_AB_2_2
	g_AB_2_2 = arr_AB_2_2
	global g_AB_3_1
	g_AB_3_1 = arr_AB_3_1
	global g_AB_3_2
	g_AB_3_2 = arr_AB_3_2
	global g_AB_4_1
	g_AB_4_1 = arr_AB_4_1
	global g_AB_4_2
	g_AB_4_2 = arr_AB_4_2
	global g_AB_5_1
	g_AB_5_1 = arr_AB_5_1
	global g_AB_5_2
	g_AB_5_2 = arr_AB_5_2
	global g_AB_6_1
	g_AB_6_1 = arr_AB_6_1
	global g_AB_6_2
	g_AB_6_2 = arr_AB_6_2
	global g_I
	g_I = arr_I


def init_pool_2(arr_AB_1_3,arr_AB_1_4,
				arr_AB_2_3,arr_AB_2_4,
				arr_AB_3_3,arr_AB_3_4,
				arr_AB_4_3,arr_AB_4_4,
				arr_AB_5_3,arr_AB_5_4,
				arr_AB_6_3,arr_AB_6_4,
				arr_O):
	global g_AB_1_3
	g_AB_1_3 = arr_AB_1_3
	global g_AB_1_4
	g_AB_1_4 = arr_AB_1_4
	global g_AB_2_3
	g_AB_2_3 = arr_AB_2_3
	global g_AB_2_4
	g_AB_2_4 = arr_AB_2_4
	global g_AB_3_3
	g_AB_3_3 = arr_AB_3_3
	global g_AB_3_4
	g_AB_3_4 = arr_AB_3_4
	global g_AB_4_3
	g_AB_4_3 = arr_AB_4_3
	global g_AB_4_4
	g_AB_4_4 = arr_AB_4_4
	global g_AB_5_3
	g_AB_5_3 = arr_AB_5_3
	global g_AB_5_4
	g_AB_5_4 = arr_AB_5_4
	global g_AB_6_3
	g_AB_6_3 = arr_AB_6_3
	global g_AB_6_4
	g_AB_6_4 = arr_AB_6_4
	global g_O
	g_O = arr_O


def divided_algo():
	AB_1_1 = np.kron(A_1_1, B_1_1)
	AB_1_2 = np.kron(A_1_2, B_1_2)
	AB_1_3 = np.kron(A_1_3, B_1_3)
	AB_1_4 = np.kron(A_1_4, B_1_4)
	AB_2_1 = np.kron(A_2_1, B_2_1)
	AB_2_2 = np.kron(A_2_2, B_2_2)
	AB_2_3 = np.kron(A_2_3, B_2_3)
	AB_2_4 = np.kron(A_2_4, B_2_4)
	AB_3_1 = np.kron(A_3_1, B_3_1)
	AB_3_2 = np.kron(A_3_2, B_3_2)
	AB_3_3 = np.kron(A_3_3, B_3_3)
	AB_3_4 = np.kron(A_3_4, B_3_4)
	AB_4_1 = np.kron(A_4_1, B_4_1)
	AB_4_2 = np.kron(A_4_2, B_4_2)
	AB_4_3 = np.kron(A_4_3, B_4_3)
	AB_4_4 = np.kron(A_4_4, B_4_4)
	AB_5_1 = np.kron(A_5_1, B_5_1)
	AB_5_2 = np.kron(A_5_2, B_5_2)
	AB_5_3 = np.kron(A_5_3, B_5_3)
	AB_5_4 = np.kron(A_5_4, B_5_4)
	AB_6_1 = np.kron(A_6_1, B_6_1)
	AB_6_2 = np.kron(A_6_2, B_6_2)
	AB_6_3 = np.kron(A_6_3, B_6_3)
	AB_6_4 = np.kron(A_6_4, B_6_4)

	arr_AB_1_1 = mp.RawArray('d', AB_1_1.ravel())
	arr_AB_1_2 = mp.RawArray('d', AB_1_2.ravel())	
	arr_AB_2_1 = mp.RawArray('d', AB_2_1.ravel())
	arr_AB_2_2 = mp.RawArray('d', AB_2_2.ravel())	
	arr_AB_3_1 = mp.RawArray('d', AB_3_1.ravel())
	arr_AB_3_2 = mp.RawArray('d', AB_3_2.ravel())	
	arr_AB_4_1 = mp.RawArray('d', AB_4_1.ravel())
	arr_AB_4_2 = mp.RawArray('d', AB_4_2.ravel())
	arr_AB_5_1 = mp.RawArray('d', AB_5_1.ravel())
	arr_AB_5_2 = mp.RawArray('d', AB_5_2.ravel())
	arr_AB_6_1 = mp.RawArray('d', AB_6_1.ravel())
	arr_AB_6_2 = mp.RawArray('d', AB_6_2.ravel())
	I = mp.RawArray('d', X.ravel())

	with mp.Pool(processes=6, initializer=init_pool_1, initargs=(arr_AB_1_1,arr_AB_1_2,arr_AB_2_1,arr_AB_2_2,arr_AB_3_1,arr_AB_3_2,arr_AB_4_1,arr_AB_4_2,arr_AB_5_1,arr_AB_5_2,arr_AB_6_1,arr_AB_6_2,I,)) as pool:
		start_t_1 = datetime.datetime.now()
		X_1 = pool.apply_async(single_algo_1, args=(1,1,1,2,))
		X_2 = pool.apply_async(single_algo_1, args=(2,1,2,2,))
		X_3 = pool.apply_async(single_algo_1, args=(3,1,3,2,))
		X_4 = pool.apply_async(single_algo_1, args=(4,1,4,2,))
		X_5 = pool.apply_async(single_algo_1, args=(5,1,5,2,))
		X_6 = pool.apply_async(single_algo_1, args=(6,1,6,2,))
		Y = X_1.get() + X_2.get() + X_3.get() + X_4.get() + X_5.get() + X_6.get()
		end_t_1 = datetime.datetime.now()

	arr_AB_1_3 = mp.RawArray('d', AB_1_3.ravel())
	arr_AB_1_4 = mp.RawArray('d', AB_1_4.ravel())
	arr_AB_2_3 = mp.RawArray('d', AB_2_3.ravel())
	arr_AB_2_4 = mp.RawArray('d', AB_2_4.ravel())
	arr_AB_3_3 = mp.RawArray('d', AB_3_3.ravel())
	arr_AB_3_4 = mp.RawArray('d', AB_3_4.ravel())
	arr_AB_4_3 = mp.RawArray('d', AB_4_3.ravel())
	arr_AB_4_4 = mp.RawArray('d', AB_4_4.ravel())
	arr_AB_5_3 = mp.RawArray('d', AB_5_3.ravel())
	arr_AB_5_4 = mp.RawArray('d', AB_5_4.ravel())
	arr_AB_6_3 = mp.RawArray('d', AB_6_3.ravel())
	arr_AB_6_4 = mp.RawArray('d', AB_6_4.ravel())
	O = mp.RawArray('d', Y.ravel())

	with mp.Pool(processes=6, initializer=init_pool_2, initargs=(arr_AB_1_3,arr_AB_1_4,arr_AB_2_3,arr_AB_2_4,arr_AB_3_3,arr_AB_3_4,arr_AB_4_3,arr_AB_4_4,arr_AB_5_3,arr_AB_5_4,arr_AB_6_3,arr_AB_6_4,O,)) as pool:
		start_t_2 = datetime.datetime.now()
		X_1 = pool.apply_async(single_algo_2, args=(1,3,1,4,))
		X_2 = pool.apply_async(single_algo_2, args=(2,3,2,4,))
		X_3 = pool.apply_async(single_algo_2, args=(3,3,3,4,))
		X_4 = pool.apply_async(single_algo_2, args=(4,3,4,4,))
		X_5 = pool.apply_async(single_algo_2, args=(5,3,5,4,))
		X_6 = pool.apply_async(single_algo_2, args=(6,3,6,4,))
		Y = X_1.get() + X_2.get() + X_3.get() + X_4.get() + X_5.get() + X_6.get()
		end_t_2 = datetime.datetime.now()

	print('Whole parallel time: %lf' % ((end_t_2 - start_t_2).total_seconds() + (end_t_1 - start_t_1).total_seconds()))
	g_lst_parallel_times.append(((end_t_2 - start_t_2).total_seconds() + (end_t_1 - start_t_1).total_seconds()))

	return Y


if __name__ == '__main__':
	for i in range(10):
		Y_1 = normal_algo()
		Y_2 = divided_algo()
	
	print('Normal is:')
	print(Y_1.shape)
	print(Y_1)
	print('\n')

	print('Divided is:')
	print(Y_2.shape)
	print(Y_2)
	print('\n')

	print('Avg Serial Time: %lf. Avg Parallel Time: %lf' % (np.mean(np.array(g_lst_serial_times)), np.mean(np.array(g_lst_parallel_times))))
	print('\n')

